package main

import (
	"context"
	"fmt"
	"go.etcd.io/etcd/clientv3"
	"time"
)

func main() {
	// 分布式乐观锁
	// 实现原理，抢先占用一个key，并且设置自动续约，如果当申请的时候发现key已经存在， 则抢锁失败
	var (
		config         clientv3.Config
		client         *clientv3.Client
		err            error
		lease          clientv3.Lease
		leaseGrantResp *clientv3.LeaseGrantResponse
		leaseId        clientv3.LeaseID
		ctx            context.Context
		cancelFun      context.CancelFunc
		keepRespCahn   <-chan *clientv3.LeaseKeepAliveResponse
		keepResp       *clientv3.LeaseKeepAliveResponse
		kv             clientv3.KV
		txn            clientv3.Txn
		keys           string
		txnResp        *clientv3.TxnResponse
	)

	// 客户端配置
	config = clientv3.Config{
		Endpoints:   []string{"127.0.0.1:2379"}, // 连接客户端
		DialTimeout: 5 * time.Second,            // 设置超时时间
	}

	// 建立连接
	if client, err = clientv3.New(config); err != nil {
		fmt.Println(err)
		return
	}
	// 上锁 （创建租约，自动续租，拿着租约去抢占一个key）
	lease = clientv3.NewLease(client)

	// 申请一个5秒的租约
	if leaseGrantResp, err = lease.Grant(context.TODO(), 5); err != nil {
		fmt.Println(err)
		return
	}

	// 拿到续约id
	leaseId = leaseGrantResp.ID

	// 准备一个用于取消自动续费的上下文
	ctx, cancelFun = context.WithCancel(context.TODO())

	// 确保函数退出后续约自动停止
	defer cancelFun()
	defer lease.Revoke(context.TODO(), leaseId)

	// 5秒后会自动续约
	if keepRespCahn, err = lease.KeepAlive(ctx, leaseId); err != nil {
		fmt.Println(err)
		return
	}

	// 处理自动续约应答协程
	go func() {
		for {
			select {
			case keepResp = <-keepRespCahn:
				if keepResp == nil {
					fmt.Println("续约已经失效")
					goto END
				} else {
					fmt.Println("收到自动续约应答", keepResp.ID)
				}

			}
		}
	END:
	}()

	// if 不存在key then 则设置它  else 枪锁失败
	kv = clientv3.NewKV(client)

	// 创建事务
	txn = kv.Txn(context.TODO())

	keys = "/cron/lock/job9"
	txn.If(clientv3.Compare(clientv3.CreateRevision(keys), "=", 0)).
		Then(clientv3.OpPut(keys, "xxx", clientv3.WithLease(leaseId))).
		Else(clientv3.OpGet(keys))

	// 提交事务
	if txnResp, err = txn.Commit(); err != nil {
		fmt.Println(err)
		return
	}

	// 判断是否抢到了锁
	if !txnResp.Succeeded {
		fmt.Println("锁被占用:", string(txnResp.Responses[0].GetResponseRange().Kvs[0].Value))
		return
	}

	// 处理业务
	fmt.Println("业务处理")
	time.Sleep(10 * time.Second)

	// 释放锁
	// defer已经处理

}
